package common

import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.model.headers.Allow
import akka.http.scaladsl.server._
import helpers._

import scala.compat.Platform.EOL

trait ExtendRequestDirectives extends Directives {
  this: JsonProtocol with Logging ⇒

  //ExtendRejectionHandler
  //@formatter:off
  protected def rejectionHandler: RejectionHandler = RejectionHandler.newBuilder()
    .handleAll[SchemeRejection] { rejections ⇒
      val schemes = rejections.map(_.supported).mkString(", ")
      complete((BadRequest, ApiResponse.Error(error = "Uri scheme not allowed, supported schemes: " + schemes)))
    }
    .handleAll[MethodRejection] { rejections ⇒
      val (methods, names) = rejections.map(r ⇒ r.supported → r.supported.name).unzip
      complete((MethodNotAllowed, List(Allow(methods)), ApiResponse.Error(error = "HTTP method not allowed, supported methods: " + names.mkString(", "))))
    }
    .handle {
      case AuthorizationFailedRejection ⇒
        complete(Forbidden, ApiResponse.Error(error = "The supplied authentication is not authorized to access this resource"))
    }
    .handle {
      case MalformedRequestContentRejection(msg, t) ⇒
        complete(BadRequest, ApiResponse.Error(message = "The request content was malformed:\n" + msg, error = t.getStackTrace.mkString("", EOL, EOL)))
    }
    .handle {
      case ValidationRejection(message, cause) ⇒
        Logger.debug("{}", cause)
        complete((BadRequest, ApiResponse.Error(error = message)))
    }
    .handleNotFound(complete((NotFound, ApiResponse.Error(error = "The requested resource could not be found."))))
    .result()
    .withFallback(RejectionHandler.default)
  //@formatter:on

  //ExtendRejectionHandler
  protected def exceptionHandler = ExceptionHandler {
    //    case NonFatal(ex)     ⇒
    //      Logger.error(s"Unexpected exception thrown whilst processing connection handler routes! [${ex.getMessage}] \n {$ex}")
    //      complete(StatusCodes.InternalServerError, ApiResponse.Error(message = "Internal server error", error = ex.getMessage))
    case e: IllegalArgumentException ⇒
      complete(StatusCodes.BadRequest, ApiResponse.Error(message = "Business logic error", error = e.getMessage))
    case e: BaseException ⇒
      Logger.error(s"Exception during client request processing: [${e.getMessage}] \n {$e}")
      complete(StatusCodes.BadRequest, ApiResponse.Error(message = "Business logic error", error = e.getMessage))
    case e: Exception ⇒
      Logger.error(s"Exception during client request processing:  [${e.getMessage}] \n {$e}")
      complete(StatusCodes.InternalServerError, ApiResponse.Error(message = "Internal server error", error = e.getMessage))
    case e: Throwable ⇒
      Logger.error(s"Exception during client request processing:  [${e.getMessage}] \n {$e}")
      complete(StatusCodes.InternalServerError, ApiResponse.Error(message = "Internal server error", error = e.getMessage))
  }

  protected val logDuration: Directive0 = extractRequestContext.flatMap { ctx ⇒
    Logger.info(s"[${ctx.request.method.name}] ${ctx.request.uri}")
    val start = System.currentTimeMillis()
    // handling rejections here so that we get proper status codes
    mapResponse { resp ⇒
      val d = System.currentTimeMillis() - start
      Logger.info(s"[${resp.status.intValue()}] ${ctx.request.method.name} ${ctx.request.uri} took: ${d}ms")
      resp
    } & handleRejections(rejectionHandler)
  }

  protected val requestWrapper: Directive0 = logDuration &
    handleExceptions(exceptionHandler) &
    encodeResponse
}
